
package msu.ml.data.level2;

import java.io.*;

/**
 * Build10Packet stores radial NEXRAD radar data.
 *
 * Build10Packet <a href="http://crane.cs.montana.edu/cgi-bin/cvsweb.cgi/mltoolkit/src/msu/ml/data/level2/Build10Packet.java">[CVS]</a> is used to read
 * packets of data stored in the new NEXRAD Build 10 format.
 *
 * @author Reginald M Mead
 * @version 1.0
 */
public class Build10Packet extends PacketBase
{

   /**
    * Create a new empty packet
    */
   public Build10Packet()
   {
      this(null);
   }

   /**
    * Create a new Build10Packet by reading the next
    * packet of data from the provided input stream.
    *
    * @param stream input stream to read data from
    */
   public Build10Packet(Level2InputStream stream)
   {
      super(stream);

      identifier = new char[4];
      momentDataPointers = new int[6];

      if(stream != null)
      {
         try
         {
            if(msgType != 31)
            {
               stream.skip(2404);
            }
            else
            {
               stream.readBytes(identifier, 0, 4);
               time = stream.readInt();
               date = stream.readShort();

               azimuthNumber = stream.readShort();
               azimuthAngle = stream.readFloat();
               compression = stream.readByte();
               stream.skip(1);
               radialLength = stream.readShort();
               azimuthResolution = stream.readByte();
               radialStatus = stream.readByte();
               elevationNumber = stream.readByte();
               cutSectorNumber = stream.readByte();
               elevationAngle = stream.readFloat();
               spotBlanking = stream.readByte();
               azimuthIndexingMode = stream.readByte();
               dataBlockCount = stream.readShort();
               volumeDataPointer = stream.readInt();
               elevationDataPointer = stream.readInt();
               radialDataPointer = stream.readInt();

               for(int i = 0; i < 6; i++)
                  momentDataPointers[i] = stream.readInt();

               int currentPointer = 0x44;

               if(dataBlockCount > 0)
               {
                  if(volumeDataPointer > currentPointer)
                     stream.skip(volumeDataPointer - currentPointer);

                  volumeBlock = new VolumeDataBlock(stream);
                  currentPointer = volumeDataPointer + 44;
               }

               if(dataBlockCount > 1)
               {
                  if(elevationDataPointer > currentPointer)
                     stream.skip(elevationDataPointer - currentPointer);

                  elevationBlock = new ElevationDataBlock(stream);
                  currentPointer = elevationDataPointer + 12;
               }

               if(dataBlockCount > 2)
               {
                  if(radialDataPointer > currentPointer)
                     stream.skip(radialDataPointer - currentPointer);

                  radialBlock = new RadialDataBlock(stream);
                  currentPointer = radialDataPointer + 20;
               }

               momentBlocks = new MomentDataBlock[dataBlockCount - 3];
               for(int i = 0; i < momentBlocks.length; i++)
               {
                  if(momentDataPointers[i] > currentPointer)
                     stream.skip(momentDataPointers[i] - currentPointer);

                  momentBlocks[i] = new MomentDataBlock(stream);
                  currentPointer = (momentDataPointers[i] + 
                        momentBlocks[i].blockSize());
               }

               int endPointer = (msgSize * 2) - 0x10;
               if(currentPointer < endPointer)
                  stream.skip(endPointer - currentPointer);
            }
         }
         catch(IOException e)
         {
            System.out.println(e.getMessage());
         }
      }
   }

   /**
    *
    * @return true if packet contains radial radar data
    */
   public boolean isRadialData()
   {
      return getMessageType() == 31;
   }

   public double getAtmosphericAttenuation()
   {
      return elevationBlock.attenuation;
   }

   public int getRayTime()
   {
      return time;
   }

   public int getRayDate()
   {
      return date;
   }

   public int getUnambiguousRange()
   {
      return radialBlock.unambiguousRange;
   }

   public double getAzimuth()
   {
      return azimuthAngle;
   }

   public int getRayNumber()
   {
      return azimuthNumber;
   }

   public int getRayStatus()
   {
      return radialStatus;
   }

   public double getElevation()
   {
      return elevationAngle;
   }

   public int getElevationNumber()
   {
      return elevationNumber;
   }

   public double getRayWidth()
   {
      return (azimuthResolution == 1) ? 0.5 : 1.0;
   }

   public int getReflectivityRange()
   {
      MomentDataBlock data = findMoment("REF");
      return (data == null) ? 0 : data.range;
   }

   public int getReflectivityGateSize()
   {
      MomentDataBlock data = findMoment("REF");
      return (data == null) ? 0 : data.gateSize;
   }

   public int getReflectivityGates()
   {
      MomentDataBlock data = findMoment("REF");
      return (data == null) ? 0 : data.gates;
   }

   public int getDopplerRange()
   {
      MomentDataBlock data = findMoment("VEL");
      return (data == null) ? 0 : data.range;
   }

   public int getDopplerGates()
   {
      MomentDataBlock data = findMoment("VEL");
      return (data == null) ? 0 : data.gates;
   }

   public int getDopplerGateSize()
   {
      MomentDataBlock data = findMoment("VEL");
      return (data == null) ? 0 : data.gateSize;
   }

   public int getSectorNumber()
   {
      return 0;
   }

   public double getSystemCalibration()
   {
      return elevationBlock.calibration;
   }

   public int getVelocityResolution()
   {
      return 0;
   }

   public int getVolumeCoveragePattern()
   {
      return volumeBlock.vcp;
   }

   public int getNyquistVelocity()
   {
      return radialBlock.nyquistVelocity;
   }

   public int getSNRThreshold()
   {
      return findMoment("REF").snr;
   }

   public double [] getReflectivityData()
   {
      return getDataForMoment(findMoment("REF"));
   }

   public double [] getVelocityData()
   {
      return getDataForMoment(findMoment("VEL"));
   }

   public double [] getSpectrumWidthData()
   {
      return getDataForMoment(findMoment("SW "));
   }

   private double [] getDataForMoment(MomentDataBlock block)
   {
      if(block == null)
         return new double[0];

      double [] result = new double[block.gates];
      for(int i = 0; i < block.gates; i++)
      {
         if(block.data[i] == 0)
            result[i] = Ray.BADVAL;
         else if(block.data[i] == 1)
            result[i] = Ray.RFVAL;
         else
            result[i] = ((float)block.data[i] - block.offset) / block.scale;
      }

      return result;
   }

   private MomentDataBlock findMoment(String name)
   {
      for(int i = 0; i < momentBlocks.length; i++)
         if(momentBlocks[i].getName().equals(name))
            return momentBlocks[i];
      return null;
   }

   private static String formatString = "%1$s\nIdentifier: %2$s\nDate: %3$s\nTime: %25$d\nAzimuth Number: %4$d\nAzimuth Angle: %5$.2f\nCompression: %6$d\nRadial Length: %7$d\nAzimuth Resolution: %8$d\nRadial Status: %9$d\nElevation Number: %10$d\nCut Sector Number: %11$d\nElevation Angle: %12$.2f\nSpot Blanking: %13$d\nAzimuth Indexing Mode: %14$d\nData Block Count: %15$d\nVolume Data Pointer: %16$d\nElevation Data Pointer: %17$d\nRadial Data Pointer: %18$d\nReflectivity Data Pointer: %19$d\nVelocity Data Pointer: %20$d\nSpectrum Width Data Pointer: %21$d\n%22$s\n%23$s\n%24$s";

   public String toString()
   {
      String result = String.format(Build10Packet.formatString, super.toString(), 
            new String(identifier), Ray.convertJulianToDate(date, time), azimuthNumber, 
            azimuthAngle, compression, radialLength, azimuthResolution, 
            radialStatus, elevationNumber, cutSectorNumber, elevationAngle, 
            spotBlanking, azimuthIndexingMode, dataBlockCount, volumeDataPointer,
            elevationDataPointer, radialDataPointer, momentDataPointers[0], 
            momentDataPointers[1], momentDataPointers[2], volumeBlock, elevationBlock, 
            radialBlock, time);

      if(momentBlocks != null)
         for(int i = 0; i < momentBlocks.length; i++)
            result += ("\n" + momentBlocks[i].toString());

      return result;
   }

   protected char[] identifier;
   protected int time;
   protected short date;
   protected short azimuthNumber;
   protected double azimuthAngle;
   protected byte compression;
   protected short radialLength;
   protected byte azimuthResolution;
   protected byte radialStatus;
   protected byte elevationNumber;
   protected byte cutSectorNumber;
   protected double elevationAngle;
   protected byte spotBlanking;
   protected byte azimuthIndexingMode;
   protected short dataBlockCount;
   protected int volumeDataPointer;
   protected int elevationDataPointer;
   protected int radialDataPointer;
   protected int [] momentDataPointers;

   public VolumeDataBlock volumeBlock;
   public ElevationDataBlock elevationBlock;
   public RadialDataBlock radialBlock;
   public MomentDataBlock [] momentBlocks;
}
